
/*-------------------------------------------------------------------------
 *
 * fe-auth.c
 *	   前端（客户端）授权例程
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-auth.c
 *
 *-------------------------------------------------------------------------
 */

/*
 * 接口例程
 *	   前端（客户端）例程：
 *		pg_fe_sendauth			发送认证信息
 *		pg_fe_getauthname		根据认证系统的客户端获取用户的名称
 */

#include "postgres_fe.h"

#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <fcntl.h>
#include <sys/param.h>			/* 对于大多数情况的 MAXHOSTNAMELEN */
#include <sys/socket.h>
#ifdef HAVE_SYS_UCRED_H
#include <sys/ucred.h>
#endif
#ifndef  MAXHOSTNAMELEN
#include <netdb.h>				/* 对于某些情况的 MAXHOSTNAMELEN */
#endif
#endif

#include "common/md5.h"
#include "common/scram-common.h"
#include "fe-auth.h"
#include "fe-auth-sasl.h"
#include "libpq-fe.h"

#ifdef FDDGM
#include "common/fdb_sm3.h"
#endif

#ifdef ENABLE_GSS
/*
 * GSSAPI 认证系统。
 */

#include "fe-gssapi-common.h"

/*
 * 根据需要继续进行 GSS 认证，使用下一个标记。
 */
static int fc_pg_GSS_continue(PGconn *fc_conn, int fc_payloadlen)
{
	OM_uint32	fc_maj_stat,
				fc_min_stat,
				fc_lmin_s;
	gss_buffer_desc fc_ginbuf;
	gss_buffer_desc fc_goutbuf;

	/*
	 * 在第一次调用时，没有输入标记。在后续调用中，将输入标记读取到 GSS 缓冲区中。
	 */
	if (fc_conn->gctx != GSS_C_NO_CONTEXT)
	{
		fc_ginbuf.length = fc_payloadlen;
		fc_ginbuf.value = malloc(fc_payloadlen);
		if (!fc_ginbuf.value)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("out of memory allocating GSSAPI buffer (%d)\n"),
							  fc_payloadlen);
			return STATUS_ERROR;
		}
		if (pqGetnchar(fc_ginbuf.value, fc_payloadlen, fc_conn))
		{
			/*
			 * 不应该发生，因为调用者应该已确保整个消息已经在输入缓冲区中。
			 */
			free(fc_ginbuf.value);
			return STATUS_ERROR;
		}
	}
	else
	{
		fc_ginbuf.length = 0;
		fc_ginbuf.value = NULL;
	}

	fc_maj_stat = gss_init_sec_context(&fc_min_stat,
									GSS_C_NO_CREDENTIAL,
									&fc_conn->gctx,
									fc_conn->gtarg_nam,
									GSS_C_NO_OID,
									GSS_C_MUTUAL_FLAG,
									0,
									GSS_C_NO_CHANNEL_BINDINGS,
									(fc_ginbuf.value == NULL) ? GSS_C_NO_BUFFER : &fc_ginbuf,
									NULL,
									&fc_goutbuf,
									NULL,
									NULL);

	if (fc_ginbuf.value)
		free(fc_ginbuf.value);

	if (fc_goutbuf.length != 0)
	{
		/*
		 * GSS 生成的数据以发送到服务器。我们不关心这是第一个包还是后续包，只需发送相同类型的密码包。
		 */
		if (pqPacketSend(fc_conn, 'p',
						 fc_goutbuf.value, fc_goutbuf.length) != STATUS_OK)
		{
			gss_release_buffer(&fc_lmin_s, &fc_goutbuf);
			return STATUS_ERROR;
		}
	}
	gss_release_buffer(&fc_lmin_s, &fc_goutbuf);

	if (fc_maj_stat != GSS_S_COMPLETE && fc_maj_stat != GSS_S_CONTINUE_NEEDED)
	{
		pg_GSS_error(libpq_gettext("GSSAPI continuation error"),
					 fc_conn,
					 fc_maj_stat, fc_min_stat);
		gss_release_name(&fc_lmin_s, &fc_conn->gtarg_nam);
		if (fc_conn->gctx)
			gss_delete_sec_context(&fc_lmin_s, &fc_conn->gctx, GSS_C_NO_BUFFER);
		return STATUS_ERROR;
	}

	if (fc_maj_stat == GSS_S_COMPLETE)
		gss_release_name(&fc_lmin_s, &fc_conn->gtarg_nam);

	return STATUS_OK;
}

/*
 * 发送初始 GSS 认证标记
 */
static int fc_pg_GSS_startup(PGconn *fc_conn, int fc_payloadlen)
{
	int			fc_ret;
	char	   *fc_host = fc_conn->connhost[fc_conn->whichhost].host;

	if (!(fc_host && fc_host[0] != '\0'))
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("host name must be specified\n"));
		return STATUS_ERROR;
	}

	if (fc_conn->gctx)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("duplicate GSS authentication request\n"));
		return STATUS_ERROR;
	}

	fc_ret = pg_GSS_load_servicename(fc_conn);
	if (fc_ret != STATUS_OK)
		return fc_ret;

	/*
	 * 初始数据包与没有初始上下文的续包相同。
	 */
	fc_conn->gctx = GSS_C_NO_CONTEXT;

	return fc_pg_GSS_continue(fc_conn, fc_payloadlen);
}
#endif							/* ENABLE_GSS */


#ifdef ENABLE_SSPI
/*
 * SSPI 认证系统（仅限 Windows）
 */

static void pg_SSPI_error(PGconn *conn, const char *mprefix, SECURITY_STATUS r)
{
	char		sysmsg[256];

	if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
					  FORMAT_MESSAGE_FROM_SYSTEM,
					  NULL, r, 0,
					  sysmsg, sizeof(sysmsg), NULL) == 0)
		appendPQExpBuffer(&conn->errorMessage, "%s: SSPI error %x\n",
						  mprefix, (unsigned int) r);
	else
		appendPQExpBuffer(&conn->errorMessage, "%s: %s (%x)\n",
						  mprefix, sysmsg, (unsigned int) r);
}

/*
 * 根据需要继续 SSPI 认证，使用下一个标记。
 */
static int pg_SSPI_continue(PGconn *conn, int payloadlen)
{
	SECURITY_STATUS r;
	CtxtHandle	newContext;
	ULONG		contextAttr;
	SecBufferDesc inbuf;
	SecBufferDesc outbuf;
	SecBuffer	OutBuffers[1];
	SecBuffer	InBuffers[1];
	char	   *inputbuf = NULL;

	if (conn->sspictx != NULL)
	{
		/*
		 * 在第一次运行以外的情况下，我们有一些数据要发送。将这些数据放入 SecBuffer 类型结构中。
		 */
		inputbuf = malloc(payloadlen);
		if (!inputbuf)
		{
			appendPQExpBuffer(&conn->errorMessage,
							  libpq_gettext("out of memory allocating SSPI buffer (%d)\n"),
							  payloadlen);
			return STATUS_ERROR;
		}
		if (pqGetnchar(inputbuf, payloadlen, conn))
		{
			/*
			 * 不应该发生，因为调用者应该已确保整个消息已经在输入缓冲区中。
			 */
			free(inputbuf);
			return STATUS_ERROR;
		}

		inbuf.ulVersion = SECBUFFER_VERSION;
		inbuf.cBuffers = 1;
		inbuf.pBuffers = InBuffers;
		InBuffers[0].pvBuffer = inputbuf;
		InBuffers[0].cbBuffer = payloadlen;
		InBuffers[0].BufferType = SECBUFFER_TOKEN;
	}

	OutBuffers[0].pvBuffer = NULL;
	OutBuffers[0].BufferType = SECBUFFER_TOKEN;
	OutBuffers[0].cbBuffer = 0;
	outbuf.cBuffers = 1;
	outbuf.pBuffers = OutBuffers;
	outbuf.ulVersion = SECBUFFER_VERSION;

	r = InitializeSecurityContext(conn->sspicred,
								  conn->sspictx,
								  conn->sspitarget,
								  ISC_REQ_ALLOCATE_MEMORY,
								  0,
								  SECURITY_NETWORK_DREP,
								  (conn->sspictx == NULL) ? NULL : &inbuf,
								  0,
								  &newContext,
								  &outbuf,
								  &contextAttr,
								  NULL);

	/* 我们不再需要输入 */
	if (inputbuf)
		free(inputbuf);

	if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
	{
		pg_SSPI_error(conn, libpq_gettext("SSPI continuation error"), r);

		return STATUS_ERROR;
	}

	if (conn->sspictx == NULL)
	{
		/* 在第一次运行时，转移检索到的上下文句柄 */
		conn->sspictx = malloc(sizeof(CtxtHandle));
		if (conn->sspictx == NULL)
		{
			appendPQExpBufferStr(&conn->errorMessage,
								 libpq_gettext("out of memory\n"));
			return STATUS_ERROR;
		}
		memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle));
	}

	/*
	 * 如果 SSPI 返回了任何要发送到服务器的数据（通常会这样），则将这些数据作为密码包发送。
	 */
	if (outbuf.cBuffers > 0)
	{
		if (outbuf.cBuffers != 1)
		{
			/*
			 * 这永远不应该发生，至少对于 Kerberos 认证来说。请保持检查，以防它在以后出现其他认证方法时出现。
			 */
			appendPQExpBufferStr(&conn->errorMessage,
								 "SSPI returned invalid number of output buffers\n");
			return STATUS_ERROR;
		}

		/*
		 * 如果谈判已完成，可能没有字节要发送。此时，服务器不再期待任何更多数据，因此不要发送它。
		 */
		if (outbuf.pBuffers[0].cbBuffer > 0)
		{
			if (pqPacketSend(conn, 'p',
							 outbuf.pBuffers[0].pvBuffer, outbuf.pBuffers[0].cbBuffer))
			{
				FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
				return STATUS_ERROR;
			}
		}
		FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
	}

	/* 清理由 freePGconn() 中的代码处理 */
	return STATUS_OK;
}

/*
 * 发送初始 SSPI 认证标记。
 * 如果 use_negotiate 为 0，则使用与 Unix 兼容的 kerberos 认证包。如果 use_negotiate 为 1，则使用支持 kerberos 和 NTLM 的 negotiate 包，但与 Unix 不兼容。
 */
static int pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
{
	SECURITY_STATUS r;
	TimeStamp	expire;
	char	   *host = conn->connhost[conn->whichhost].host;

	if (conn->sspictx)
	{
		appendPQExpBufferStr(&conn->errorMessage,
							 libpq_gettext("duplicate SSPI authentication request\n"));
		return STATUS_ERROR;
	}

	/*
	 * 检索凭据句柄
	 */
	conn->sspicred = malloc(sizeof(CredHandle));
	if (conn->sspicred == NULL)
	{
		appendPQExpBufferStr(&conn->errorMessage,
							 libpq_gettext("out of memory\n"));
		return STATUS_ERROR;
	}

	r = AcquireCredentialsHandle(NULL,
								 use_negotiate ? "negotiate" : "kerberos",
								 SECPKG_CRED_OUTBOUND,
								 NULL,
								 NULL,
								 NULL,
								 NULL,
								 conn->sspicred,
								 &expire);
	if (r != SEC_E_OK)
	{
		pg_SSPI_error(conn, libpq_gettext("could not acquire SSPI credentials"), r);
		free(conn->sspicred);
		conn->sspicred = NULL;
		return STATUS_ERROR;
	}

	/*
	 * 计算目标主体名称。SSPI 的格式与 GSSAPI 不同，但没有更复杂。我们可以跳过 @REALM 部分，因为 Windows 会自动为我们填写。
	 */
	if (!(host && host[0] != '\0'))
	{
		appendPQExpBufferStr(&conn->errorMessage,
							 libpq_gettext("host name must be specified\n"));
		return STATUS_ERROR;
	}
	conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(host) + 2);
	if (!conn->sspitarget)
	{
		appendPQExpBufferStr(&conn->errorMessage,
							 libpq_gettext("out of memory\n"));
		return STATUS_ERROR;
	}
	sprintf(conn->sspitarget, "%s/%s", conn->krbsrvname, host);

	/*
	 * 指示我们处于 SSPI 认证模式，以确保下次在谈判中调用 pg_SSPI_continue。
	 */
	conn->usesspi = 1;

	return pg_SSPI_continue(conn, payloadlen);
}
#endif							/* ENABLE_SSPI */

/*
 * 初始化 SASL 认证交换。
 */
static int fc_pg_SASL_init(PGconn *fc_conn, int fc_payloadlen)
{
	char	   *fc_initialresponse = NULL;
	int			fc_initialresponselen;
	bool		fc_done;
	bool		fc_success;
	const char *fc_selected_mechanism;
	PQExpBufferData fc_mechanism_buf;
	char	   *fc_password;

	initPQExpBuffer(&fc_mechanism_buf);

	if (fc_conn->channel_binding[0] == 'r' &&	/* 需要 */
		!fc_conn->ssl_in_use)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("channel binding required, but SSL not in use\n"));
		goto error;
	}

	if (fc_conn->sasl_state)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("duplicate SASL authentication request\n"));
		goto error;
	}

	/*
	 * 解析AuthenticationSASL消息中的SASL认证机制列表，并选择我们支持的最佳机制。当前支持的只有SCRAM-SHA-256-PLUS和SCRAM-SHA-256，按重要性递减顺序列出。
	 */
	fc_selected_mechanism = NULL;
	for (;;)
	{
		if (pqGets(&fc_mechanism_buf, fc_conn))
		{
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 "fe_sendauth: invalid authentication request from server: invalid list of authentication mechanisms\n");
			goto error;
		}
		if (PQExpBufferDataBroken(fc_mechanism_buf))
			goto oom_error;

		/* 空字符串表示列表结束 */
		if (fc_mechanism_buf.data[0] == '\0')
			break;

		/*
		 * 选择要使用的机制。如果设置了信道绑定类型，并且客户端支持它（并且没有设置channel_binding=disable），则优先选择SCRAM-SHA-256-PLUS。如果没有其他选择，则选择SCRAM-SHA-256。如果我们添加更多机制，可能需要更精细的优先级机制。
		 */
		if (strcmp(fc_mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0)
		{
			if (fc_conn->ssl_in_use)
			{
				/* 服务器已提供SCRAM-SHA-256-PLUS。 */

#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
				/*
				 * 客户端支持信道绑定，如果没有禁用channel_binding，则选择它。
				 */
				if (fc_conn->channel_binding[0] != 'd')	/* 禁用 */
				{
					fc_selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
					fc_conn->sasl = &pg_scram_mech;
				}
#else
				/*
				 * 客户端不支持信道绑定。如果需要，立即投诉，而不是下面的错误，这会造成混淆，因为服务器发布了SCRAM-SHA-256-PLUS。
				 */
				if (fc_conn->channel_binding[0] == 'r')	/* 需要 */
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("channel binding is required, but client does not support it\n"));
					goto error;
				}
#endif
			}
			else
			{
				/*
				 * 服务器提供了SCRAM-SHA-256-PLUS，但连接没有SSL加密。这并不合理。也许SSL被代理剥离了？继续下去没有意义，因为如果我们尝试在没有信道绑定的情况下进行身份验证，服务器无论如何会拒绝连接，即使客户端和服务器都支持它。SCRAM交换检查这一点，以防止降级攻击。
				 */
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection\n"));
				goto error;
			}
		}
		else if (strcmp(fc_mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 &&
				 !fc_selected_mechanism)
		{
			fc_selected_mechanism = SCRAM_SHA_256_NAME;
			fc_conn->sasl = &pg_scram_mech;
		}
	}

	if (!fc_selected_mechanism)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("none of the server's SASL authentication mechanisms are supported\n"));
		goto error;
	}

	if (fc_conn->channel_binding[0] == 'r' &&	/* 需要 */
		strcmp(fc_selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("channel binding is required, but server did not offer an authentication method that supports channel binding\n"));
		goto error;
	}

	/*
	 * 既然已经为交换选择了SASL机制，初始化其状态信息。
	 */

	/*
	 * 首先，选择用于交换的密码，如果没有则投诉。目前，所有支持的SASL机制都需要密码，因此我们可以在这里继续，而不需要进一步区分。
	 */
	fc_conn->password_needed = true;
	fc_password = fc_conn->connhost[fc_conn->whichhost].password;
	if (fc_password == NULL)
		fc_password = fc_conn->pgpass;
	if (fc_password == NULL || fc_password[0] == '\0')
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 PQnoPasswordSupplied);
		goto error;
	}

	Assert(fc_conn->sasl);

	/*
	 * 使用在初始交换中收集的所有信息初始化SASL状态信息。
	 *
	 * 注意：目前仅支持tls-unique。
	 */
	fc_conn->sasl_state = fc_conn->sasl->init(fc_conn,
										fc_password,
										fc_selected_mechanism);
	if (!fc_conn->sasl_state)
		goto oom_error;

	/* 获取机制特定的初始客户端响应（如果有） */
	fc_conn->sasl->exchange(fc_conn->sasl_state,
						 NULL, -1,
						 &fc_initialresponse, &fc_initialresponselen,
						 &fc_done, &fc_success);

	if (fc_done && !fc_success)
		goto error;

	/*
	 * 构建SASLInitialResponse消息，并发送它。
	 */
	if (pqPutMsgStart('p', fc_conn))
		goto error;
	if (pqPuts(fc_selected_mechanism, fc_conn))
		goto error;
	if (fc_initialresponse)
	{
		if (pqPutInt(fc_initialresponselen, 4, fc_conn))
			goto error;
		if (pqPutnchar(fc_initialresponse, fc_initialresponselen, fc_conn))
			goto error;
	}
	if (pqPutMsgEnd(fc_conn))
		goto error;
	if (pqFlush(fc_conn))
		goto error;

	termPQExpBuffer(&fc_mechanism_buf);
	if (fc_initialresponse)
		free(fc_initialresponse);

	return STATUS_OK;

error:
	termPQExpBuffer(&fc_mechanism_buf);
	if (fc_initialresponse)
		free(fc_initialresponse);
	return STATUS_ERROR;

oom_error:
	termPQExpBuffer(&fc_mechanism_buf);
	if (fc_initialresponse)
		free(fc_initialresponse);
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 libpq_gettext("out of memory\n"));
	return STATUS_ERROR;
}

/*
 * 与后端交换SASL通信协议消息。调用pg_SASL_init以设置协议状态后应使用此功能。
 */
static int fc_pg_SASL_continue(PGconn *fc_conn, int fc_payloadlen, bool fc_final)
{
	char	   *fc_output;
	int			fc_outputlen;
	bool		fc_done;
	bool		fc_success;
	int			fc_res;
	char	   *fc_challenge;

	/* 从AuthenticationSASLContinue消息中读取SASL挑战。 */
	fc_challenge = malloc(fc_payloadlen + 1);
	if (!fc_challenge)
	{
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("out of memory allocating SASL buffer (%d)\n"),
						  fc_payloadlen);
		return STATUS_ERROR;
	}

	if (pqGetnchar(fc_challenge, fc_payloadlen, fc_conn))
	{
		free(fc_challenge);
		return STATUS_ERROR;
	}
	/* 为了安全和方便，确保缓冲区以NULL结尾。 */
	fc_challenge[fc_payloadlen] = '\0';

	fc_conn->sasl->exchange(fc_conn->sasl_state,
						 fc_challenge, fc_payloadlen,
						 &fc_output, &fc_outputlen,
						 &fc_done, &fc_success);
	free(fc_challenge);			/* 不再需要输入 */

	if (fc_final && !fc_done)
	{
		if (fc_outputlen != 0)
			free(fc_output);

		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not completed\n"));
		return STATUS_ERROR;
	}

	/*
	 * 如果交换尚未完成，我们需要确保SASL机制已生成要发送回消息。
	 */
	if (fc_output == NULL && !fc_done)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("no client response found after SASL exchange success\n"));
		return STATUS_ERROR;
	}

	/*
	 * SASL允许零长度响应，因此此检查使用“output”，而不是“outputlen”，以允许空消息的情况。
	 */
	if (fc_output)
	{
		/*
		 * 将SASL响应发送到服务器。
		 */
		fc_res = pqPacketSend(fc_conn, 'p', fc_output, fc_outputlen);
		free(fc_output);

		if (fc_res != STATUS_OK)
			return STATUS_ERROR;
	}

	if (fc_done && !fc_success)
		return STATUS_ERROR;

	return STATUS_OK;
}

/*
 * 响应 AUTH_REQ_SCM_CREDS 挑战。
 *
 * 注意：自 Postgres 9.1 起这段代码已经无效，因为当前的后端
 * 永远不会发送此挑战。但只要 libpq 需要与 pre-9.1 服务器
 * 互操作，我们就必须保留它。相信它只在
 * Debian/kFreeBSD（即，FreeBSD 内核与 Linux 用户空间，所以
 * getpeereid() 函数并不由 libc 提供）中是必需的。
 */
static int fc_pg_local_sendauth(PGconn *fc_conn)
{
#ifdef HAVE_STRUCT_CMSGCRED
	char		buf;
	struct iovec iov;
	struct msghdr msg;
	struct cmsghdr *cmsg;
	union
	{
		struct cmsghdr hdr;
		unsigned char buf[CMSG_SPACE(sizeof(struct cmsgcred))];
	}			cmsgbuf;

	/*
	 * 后端不关心我们在这里发送什么，但它想要确切的一个
	 * 字符以强制 recvmsg() 阻塞并等待我们。
	 */
	buf = '\0';
	iov.iov_base = &buf;
	iov.iov_len = 1;

	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	/* 我们必须设置一个将由内核填充的消息 */
	memset(&cmsgbuf, 0, sizeof(cmsgbuf));
	msg.msg_control = &cmsgbuf.buf;
	msg.msg_controllen = sizeof(cmsgbuf.buf);
	cmsg = CMSG_FIRSTHDR(&msg);
	cmsg->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_CREDS;

	if (sendmsg(fc_conn->sock, &msg, 0) == -1)
	{
		char		sebuf[PG_STRERROR_R_BUFLEN];

		appendPQExpBuffer(&fc_conn->errorMessage,
						  "pg_local_sendauth: sendmsg: %s\n",
						  strerror_r(errno, sebuf, sizeof(sebuf)));
		return STATUS_ERROR;
	}
	return STATUS_OK;
#else
	appendPQExpBufferStr(&fc_conn->errorMessage,
						 libpq_gettext("SCM_CRED authentication method not supported\n"));
	return STATUS_ERROR;
#endif
}

static int fc_pg_password_sendauth(PGconn *fc_conn, const char *fc_password, AuthRequest fc_areq)
{
	int			fc_ret;
	char	   *fc_crypt_pwd = NULL;
	const char *fc_pwd_to_send;
	char		fc_md5Salt[4];

	/* 从 AuthenticationMD5Password 消息中读取盐。 */
#ifdef FDDGM
	if (fc_areq == AUTH_REQ_MD5 || fc_areq == AUTH_REQ_SM3)
#else
	if (fc_areq == AUTH_REQ_MD5)
#endif
	{
		if (pqGetnchar(fc_md5Salt, 4, fc_conn))
			return STATUS_ERROR;	/* 不应该发生 */
	}

	/* 如有需要，加密密码。 */

	switch (fc_areq)
	{
		case AUTH_REQ_MD5:
			{
				char	   *fc_crypt_pwd2;
				const char *fc_errstr = NULL;

				/* 为两个 MD5 哈希分配足够的空间 */
				fc_crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1));
				if (!fc_crypt_pwd)
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("out of memory\n"));
					return STATUS_ERROR;
				}

				fc_crypt_pwd2 = fc_crypt_pwd + MD5_PASSWD_LEN + 1;
				if (!pg_md5_encrypt(fc_password, fc_conn->pguser,
									strlen(fc_conn->pguser), fc_crypt_pwd2,
									&fc_errstr))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not encrypt password: %s\n"),
									  fc_errstr);
					free(fc_crypt_pwd);
					return STATUS_ERROR;
				}
				if (!pg_md5_encrypt(fc_crypt_pwd2 + strlen("md5"), fc_md5Salt,
									4, fc_crypt_pwd, &fc_errstr))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not encrypt password: %s\n"),
									  fc_errstr);
					free(fc_crypt_pwd);
					return STATUS_ERROR;
				}

				fc_pwd_to_send = fc_crypt_pwd;
				break;
			}
#ifdef FDDGM
		case AUTH_REQ_SM3:
			{
				char	   *fc_crypt_pwd2;
				const char *fc_errstr = NULL;
				fc_crypt_pwd = malloc(2 * (SM3_PASSWD_LEN + 1));
				if (!fc_crypt_pwd)
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("out of memory\n"));
					return STATUS_ERROR;
				}

				fc_crypt_pwd2 = fc_crypt_pwd + SM3_PASSWD_LEN + 1;
				if (!fdb_sm3_encrypt(fc_password, fc_conn->pguser,
									strlen(fc_conn->pguser), fc_crypt_pwd2, &fc_errstr))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not use sm3 encrypt password: %s\n"),
									  fc_errstr);
					free(fc_crypt_pwd);
					return STATUS_ERROR;
				}
				if (!fdb_sm3_encrypt(fc_crypt_pwd2 + SM3_LENGTH, fc_md5Salt, 4, fc_crypt_pwd, &fc_errstr))
				{
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("could not use sm3 encrypt password: %s\n"),
									  fc_errstr);
					free(fc_crypt_pwd);
					return STATUS_ERROR;
				}

				fc_pwd_to_send = fc_crypt_pwd;
				break;
			}
#endif
		case AUTH_REQ_PASSWORD:
			fc_pwd_to_send = fc_password;
			break;
		default:
			return STATUS_ERROR;
	}
	fc_ret = pqPacketSend(fc_conn, 'p', fc_pwd_to_send, strlen(fc_pwd_to_send) + 1);
	if (fc_crypt_pwd)
		free(fc_crypt_pwd);
	return fc_ret;
}

/*
 * 验证鉴权请求是否符合连接参数的预期。这在客户端希望
 * 在交换任何敏感信息之前鉴别服务器时尤其重要。
 */
static bool fc_check_expected_areq(AuthRequest fc_areq, PGconn *fc_conn)
{
	bool		fc_result = true;

	/*
	 * 当 channel_binding=require 时，我们必须防范两种情况： (1) 我们
	 * 必须不回应非 SASL 鉴权请求，这可能会泄露诸如客户端密码等
	 * 信息；以及 (2) 即使我们收到 AUTH_REQ_OK，我们仍必须确保
	 * 在鉴别服务器之前已经发生了通道绑定。
	 */
	if (fc_conn->channel_binding[0] == 'r' /* 需要 */ )
	{
		switch (fc_areq)
		{
			case AUTH_REQ_SASL:
			case AUTH_REQ_SASL_CONT:
			case AUTH_REQ_SASL_FIN:
				break;
			case AUTH_REQ_OK:
				if (!fc_conn->sasl || !fc_conn->sasl->channel_bound(fc_conn->sasl_state))
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("channel binding required, but server authenticated client without channel binding\n"));
					fc_result = false;
				}
				break;
			default:
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("channel binding required but not supported by server's authentication request\n"));
				fc_result = false;
				break;
		}
	}

	return fc_result;
}

/*
 * pg_fe_sendauth
 *		客户机解复用例程，用于处理鉴权请求
 *
 * 服务器已向我们发送了一个鉴权挑战（或 OK）。发送适当的
 * 响应。调用者已确保整个消息现在在输入缓冲区中，并且
 * 已经读取了消息的类型和长度。我们负责读取任何剩余的
 * 数据，特定于鉴权方法。'payloadlen' 是消息中的剩余长度。
 */
int pg_fe_sendauth(AuthRequest fc_areq, int fc_payloadlen, PGconn *fc_conn)
{
	int			fc_oldmsglen;

	if (!fc_check_expected_areq(fc_areq, fc_conn))
		return STATUS_ERROR;

	switch (fc_areq)
	{
		case AUTH_REQ_OK:
			break;

		case AUTH_REQ_KRB4:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("Kerberos 4 authentication not supported\n"));
			return STATUS_ERROR;

		case AUTH_REQ_KRB5:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("Kerberos 5 authentication not supported\n"));
			return STATUS_ERROR;

#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
		case AUTH_REQ_GSS:
#if !defined(ENABLE_SSPI)
			/* 没有原生的 SSPI，因此使用 GSSAPI 库。 */
		case AUTH_REQ_SSPI:
#endif
			{
				int			fc_r;

				pglock_thread();

				/*
				 * 如果我们同时编译了 GSS 和 SSPI 支持，默认使用
				 * SSPI 支持。这个设置可以通过连接字符串参数覆盖。
				 * 请注意，在使用 SSPI 时，我们仍然将 negotiate 参数
				 * 关闭，因为我们希望 SSPI 使用 GSSAPI kerberos 协议。
				 * 对于实际的 SSPI 协商协议，我们使用 AUTH_REQ_SSPI。
				 */
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
				if (fc_conn->gsslib && (pg_strcasecmp(fc_conn->gsslib, "gssapi") == 0))
					fc_r = pg_GSS_startup(fc_conn, fc_payloadlen);
				else
					fc_r = pg_SSPI_startup(fc_conn, 0, fc_payloadlen);
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
				fc_r = fc_pg_GSS_startup(fc_conn, fc_payloadlen);
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
				fc_r = pg_SSPI_startup(fc_conn, 0, fc_payloadlen);
#endif
				if (fc_r != STATUS_OK)
				{
					/* 错误信息已填充。 */
					pgunlock_thread();
					return STATUS_ERROR;
				}
				pgunlock_thread();
			}
			break;

		case AUTH_REQ_GSS_CONT:
			{
				int			fc_r;

				pglock_thread();
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
				if (fc_conn->usesspi)
					fc_r = pg_SSPI_continue(fc_conn, fc_payloadlen);
				else
					fc_r = pg_GSS_continue(fc_conn, fc_payloadlen);
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
				fc_r = fc_pg_GSS_continue(fc_conn, fc_payloadlen);
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
				fc_r = pg_SSPI_continue(fc_conn, fc_payloadlen);
#endif
				if (fc_r != STATUS_OK)
				{
					/* 错误信息已填充。 */
					pgunlock_thread();
					return STATUS_ERROR;
				}
				pgunlock_thread();
			}
			break;
#else							/* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */
			/* 没有 GSSAPI *或* SSPI 支持 */
		case AUTH_REQ_GSS:
		case AUTH_REQ_GSS_CONT:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("GSSAPI authentication not supported\n"));
			return STATUS_ERROR;
#endif							/* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */

#ifdef ENABLE_SSPI
		case AUTH_REQ_SSPI:

			/*
			 * SSPI 有自己的启动消息，所以 libpq 可以决定使用哪种
			 * 方法。向 pg_SSPI_startup 指示我们希望进行 SSPI
			 * 协商，而不是 Kerberos。
			 */
			pglock_thread();
			if (pg_SSPI_startup(fc_conn, 1, fc_payloadlen) != STATUS_OK)
			{
				/* 错误信息已填充。 */
				pgunlock_thread();
				return STATUS_ERROR;
			}
			pgunlock_thread();
			break;
#else

			/*
			 * 不支持 SSPI。 但是，如果我们有 GSSAPI 但没有 SSPI
			 * 支持，AUTH_REQ_SSPI 将在上面的 AUTH_REQ_GSS 的代码路径中处理，
			 * 所以在这种情况下不要重复情况标签。
			 */
#if !defined(ENABLE_GSS)
		case AUTH_REQ_SSPI:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("SSPI authentication not supported\n"));
			return STATUS_ERROR;
#endif							/* !define(ENABLE_GSS) */
#endif							/* ENABLE_SSPI */


		case AUTH_REQ_CRYPT:
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("Crypt authentication not supported\n"));
			return STATUS_ERROR;

		case AUTH_REQ_MD5:
#ifdef FDDGM
		case AUTH_REQ_SM3:
#endif
		case AUTH_REQ_PASSWORD:
			{
				char	   *fc_password;

				fc_conn->password_needed = true;
				fc_password = fc_conn->connhost[fc_conn->whichhost].password;
				if (fc_password == NULL)
					fc_password = fc_conn->pgpass;
				if (fc_password == NULL || fc_password[0] == '\0')
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 PQnoPasswordSupplied);
					return STATUS_ERROR;
				}
				if (fc_pg_password_sendauth(fc_conn, fc_password, fc_areq) != STATUS_OK)
				{
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 "fe_sendauth: error sending password authentication\n");
					return STATUS_ERROR;
				}
				break;
			}

		case AUTH_REQ_SASL:

			/*
			 * 请求包含了由 IANA 指定的认证机制的名称。
			 */
			if (fc_pg_SASL_init(fc_conn, fc_payloadlen) != STATUS_OK)
			{
				/* pg_SASL_init 已经设置了错误信息 */
				return STATUS_ERROR;
			}
			break;

		case AUTH_REQ_SASL_CONT:
		case AUTH_REQ_SASL_FIN:
			if (fc_conn->sasl_state == NULL)
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
				return STATUS_ERROR;
			}
			fc_oldmsglen = fc_conn->errorMessage.len;
			if (fc_pg_SASL_continue(fc_conn, fc_payloadlen,
								 (fc_areq == AUTH_REQ_SASL_FIN)) != STATUS_OK)
			{
				/* 如果 pg_SASL_continue 没有提供信息，则使用此信息 */
				if (fc_conn->errorMessage.len == fc_oldmsglen)
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 "fe_sendauth: error in SASL authentication\n");
				return STATUS_ERROR;
			}
			break;

		case AUTH_REQ_SCM_CREDS:
			if (fc_pg_local_sendauth(fc_conn) != STATUS_OK)
				return STATUS_ERROR;
			break;

		default:
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("authentication method %u not supported\n"), fc_areq);
			return STATUS_ERROR;
	}

	return STATUS_OK;
}


/*
 * pg_fe_getusername
 *
 * 返回一个指向动态分配空间的指针，该空间包含指定 user_id 的名称。
 * 如果发生错误，返回 NULL，并在 *errorMessage 不为 NULL 的情况下追加
 * 一个合适的错误信息。
 *
 * 注意：在 Windows 上，user_id 参数将被忽略，我们总是获取当前用户的名称。
 */
char * pg_fe_getusername(uid_t fc_user_id, PQExpBuffer fc_errorMessage)
{
	char	   *fc_result = NULL;
	const char *fc_name = NULL;

#ifdef WIN32
	/* Microsoft 推荐的缓冲区大小为 UNLEN+1，其中 UNLEN = 256 */
	char		username[256 + 1];
	DWORD		namesize = sizeof(username);
#else
	char		fc_pwdbuf[BUFSIZ];
#endif

	/*
	 * 一些用户使用 configure --enable-thread-safety-force，因此我们
	 * 可能也需要在我们的库中进行锁定以保护 getpwuid()。
	 * 实际上，如果应用程序开发者使用我们提供的锁定调用，或者使用
	 * PQregisterThreadLock() 安装自己的锁定函数，他们可以在应用程序中使用 getpwuid()。
	 */
	pglock_thread();

#ifdef WIN32
	if (GetUserName(username, &namesize))
		name = username;
	else if (errorMessage)
		appendPQExpBuffer(errorMessage,
						  libpq_gettext("user name lookup failure: error code %lu\n"),
						  GetLastError());
#else
	if (pg_get_user_name(fc_user_id, fc_pwdbuf, sizeof(fc_pwdbuf)))
		fc_name = fc_pwdbuf;
	else if (fc_errorMessage)
		appendPQExpBuffer(fc_errorMessage, "%s\n", fc_pwdbuf);
#endif

	if (fc_name)
	{
		fc_result = strdup(fc_name);
		if (fc_result == NULL && fc_errorMessage)
			appendPQExpBufferStr(fc_errorMessage,
								 libpq_gettext("out of memory\n"));
	}

	pgunlock_thread();

	return fc_result;
}

/*
 * pg_fe_getauthname
 *
 * 返回一个指向动态分配空间的指针，该空间包含用户已对系统认证的名称。
 * 如果发生错误，返回 NULL，并在 *errorMessage 不为 NULL 的情况下追加
 * 一个合适的错误信息。
 */
char * pg_fe_getauthname(PQExpBuffer fc_errorMessage)
{
#ifdef WIN32
	return pg_fe_getusername(0, errorMessage);
#else
	return pg_fe_getusername(geteuid(), fc_errorMessage);
#endif
}


/*
 * PQencryptPassword -- 导出例程，用 MD5 加密密码
 *
 * 此函数等价于调用 PQencryptPasswordConn，使用 "md5" 作为加密方法，
 * 不同的是它不需要连接对象。此函数已被弃用，请使用
 * PQencryptPasswordConn 代替。
 */
char * PQencryptPassword(const char *fc_passwd, const char *fc_user)
{
	char	   *fc_crypt_pwd;
	const char *fc_errstr = NULL;

	fc_crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
	if (!fc_crypt_pwd)
		return NULL;

	if (!pg_md5_encrypt(fc_passwd, fc_user, strlen(fc_user), fc_crypt_pwd, &fc_errstr))
	{
		free(fc_crypt_pwd);
		return NULL;
	}

	return fc_crypt_pwd;
}

/*
 * PQencryptPasswordConn -- 导出例程用于加密密码
 *
 * 这旨在被希望发送
 * 类似 ALTER USER joe PASSWORD 'pwd' 的命令的客户端应用程序使用。密码不必
 * 以明文形式发送，如果在客户端加密。这是
 * 好的，因为它确保明文密码不会出现在日志中、
 * pg_stat 显示中等。我们导出该函数，以便客户端不会
 * 依赖于低级细节，例如加密是 MD5
 * 还是其他形式。
 *
 * 参数是一个连接对象、明文密码、该密码的 SQL
 * 用户名，以及指示要使用的加密密码的算法的字符串。如果算法为 NULL，这将查询
 * 服务器当前的 'password_encryption' 值。如果您希望
 * 避免这样做，例如避免阻塞，您可以在调用此函数之前自己执行
 * 'show password_encryption'，并将其作为算法传递。
 *
 * 返回值是一个 malloc 分配的字符串。客户端可以假设该字符串
 * 不包含任何需要转义的特殊字符。
 * 出错时，错误信息存储在连接对象中，并返回 NULL。
 */
char * PQencryptPasswordConn(PGconn *fc_conn, const char *fc_passwd, const char *fc_user,
					  const char *fc_algorithm)
{
#define MAX_ALGORITHM_NAME_LEN 50
	char		fc_algobuf[MAX_ALGORITHM_NAME_LEN + 1];
	char	   *fc_crypt_pwd = NULL;

	if (!fc_conn)
		return NULL;

	pqClearConnErrorState(fc_conn);

	/* 如果未给定算法，则询问服务器。 */
	if (fc_algorithm == NULL)
	{
		PGresult   *fc_res;
		char	   *fc_val;

		fc_res = PQexec(fc_conn, "show password_encryption");
		if (fc_res == NULL)
		{
			/* PQexec() 应该已经设置 conn->errorMessage */
			return NULL;
		}
		if (PQresultStatus(fc_res) != PGRES_TUPLES_OK)
		{
			/* PQexec() 应该已经设置 conn->errorMessage */
			PQclear(fc_res);
			return NULL;
		}
		if (PQntuples(fc_res) != 1 || PQnfields(fc_res) != 1)
		{
			PQclear(fc_res);
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("unexpected shape of result set returned for SHOW\n"));
			return NULL;
		}
		fc_val = PQgetvalue(fc_res, 0, 0);

		if (strlen(fc_val) > MAX_ALGORITHM_NAME_LEN)
		{
			PQclear(fc_res);
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("password_encryption value too long\n"));
			return NULL;
		}
		strcpy(fc_algobuf, fc_val);
		PQclear(fc_res);

		fc_algorithm = fc_algobuf;
	}

	/*
	 * 也接受 "on" 和 "off" 作为 "md5" 的别名，因为
	 * password_encryption 在 PostgreSQL 10 之前是布尔值。我们拒绝
	 * 以明文形式发送密码，即使它是 "off"。
	 */
	if (strcmp(fc_algorithm, "on") == 0 ||
		strcmp(fc_algorithm, "off") == 0)
		fc_algorithm = "md5";

	/*
	 * 好吧，现在我们知道要使用哪个算法
	 */
	if (strcmp(fc_algorithm, "scram-sha-256") == 0)
	{
		const char *fc_errstr = NULL;

		fc_crypt_pwd = pg_fe_scram_build_secret(fc_passwd, &fc_errstr);
		if (!fc_crypt_pwd)
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not encrypt password: %s\n"),
							  fc_errstr);
	}
	else if (strcmp(fc_algorithm, "md5") == 0)
	{
		fc_crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
		if (fc_crypt_pwd)
		{
			const char *fc_errstr = NULL;

			if (!pg_md5_encrypt(fc_passwd, fc_user, strlen(fc_user), fc_crypt_pwd, &fc_errstr))
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not encrypt password: %s\n"),
								  fc_errstr);
				free(fc_crypt_pwd);
				fc_crypt_pwd = NULL;
			}
		}
		else
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("out of memory\n"));
	}
#ifdef FDDGM
	else if (strcmp(fc_algorithm, "sm3") == 0)
	{
		fc_crypt_pwd = malloc(SM3_PASSWD_LEN + 1);
		if (fc_crypt_pwd)
		{
			const char *fc_errstr = NULL;

			if (!fdb_sm3_encrypt(fc_passwd, fc_user, strlen(fc_user), fc_crypt_pwd, &fc_errstr))
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not encrypt password: %s\n"),
								  fc_errstr);
				free(fc_crypt_pwd);
				fc_crypt_pwd = NULL;
			}
		}
		else
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("out of memory\n"));
	}
#endif
	else
	{
		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("unrecognized password encryption algorithm \"%s\"\n"),
						  fc_algorithm);
		return NULL;
	}

	return fc_crypt_pwd;
}
